Code LLMs 是專門提供程式碼協助的大型語言模型,因為寫程式的情境剛好非常符合 Decoder LM 文字接龍的特性,因此發展比 Chat LLM 又早了一些,例如 Tabnine 與 Copilot 之類的,其實都出現的比 ChatGPT 還要早。今天就來介紹這些專業碼農而且開源的模型以及大致的用法。
StarCoder 由 BigCode 開發,與 BLOOM 的 BigScience 一樣,BigCode 也是 Hugging Face 主持的一個 Workshop 組織。StarCoder 的模型為 BigCode 他們自己研發的 GPTBigCode 架構,從 GPT-2 的架構優化修改而來。StarCoder 的前身 SantaCoder 也是使用此架構。其訓練資料被整理為 StarCoderData,包含 86 種程式語言以及各種 GitHub Issues 的資料,大小 783GB 共 250B Tokens 的文本量。
StarCoder 原先只有 15B 參數量的規格,後來陸續增加了 7B, 3B, 1B 等版本,讓資源有限的人可以更輕鬆的部署這些模型。
StarCoder 主要有三種版本:
BigCode 另外有 StarChat 模型,為 StarCoderPlus 進行 Instruction Tuning 後的版本。
若要在 Transformers 裡面調用 StarCoder 模型,需要先登入 Hugging Face Hub 並到模型頁面勾選同意,然後使用指令 huggingface-cli login
登入,接著從 Hugging Face 網站取得 Access Token,然後在 .from_pretrained
裡面透過 token
參數傳遞進去:
model = GPTBigCodeForCausalLM.from_pretrained(
"bigcode/starcoderbase-7b",
device_map="auto",
load_in_8bit=True,
token="hf_xxx",
)
tokenizer = GPT2TokenizerFast.from_pretrained(
"bigcode/starcoderbase-7b",
token="hf_xxx"
)
也可以到 SSH Key 頁面設定公鑰,然後使用 git clone
下載:
git clone git@hf.co:bigcode/starcoderbase-7b
我們可以使用 Transformers 套件來玩看看這個模型,首先我們將 Generate 包裝成函式,以方便後續調用:
from transformers import TextStreamer
tk.pad_token = tk.eos_token
ts = TextStreamer(tk)
def generate(prompt, n=42):
inputs = tk(prompt, return_tensors="pt").to("cuda")
return model.generate(**inputs, max_new_tokens=n, streamer=ts)
一般的程式碼生成,只需要透過模型本身的文字接龍能力即可達成:
generate("def fib(n: int):")
得到的輸出結果如下:
def fib(n: int):
if n == 0:
return 0
elif n == 1:
return 1
else:
return fib(n - 1) + fib(n - 2)
是個非常標準的費波那契數函式 👍
但是一般來說,寫程式時並不會總是在檔案的最尾端,因此在文字編輯器的游標前後或多或少都有其他程式碼。然而 Decoder LM 的特性使他只能往後生成文字,而不能在中間進行生成。這時候 FIM (Fill in the Middle) 訓練就派上用場了!
舉例來說,考慮以下程式碼:
def hello(name: str):
print # <= 游標在這!
def goodbye(name: str):
print(f"### 系統:再會了,{name}!")
我們根據鍵盤游標的所在位置,將程式碼切成兩半。前半段稱之為 Prefix 而後半段稱之為 Suffix,我們將 Prefix 的內容放在 <fim_prefix>
這個特殊 Token 的後面,再把 Suffix 的內容放在 <fim_suffix>
後面,並將這兩段接起來,最後放上 <fim_middle>
來發動程式碼填充的能力。其格式整體而言會像這樣:
<fim_prefix> [前半段的內容] <fim_suffix> [後半段的內容] <fim_middle>
模型能夠具有這樣的能力,就是因為在訓練的時候,FIM Training 會將挖空的程式碼片段放在 <fim_middle>
後面。如此一來,模型就可以在維持原本文字接龍能力的情況下,獲得程式碼填充的能力。接下來我們試著實際操作看看這種推論方法:
full_code = """
def hello(name: str):
print(<游標在這>)
def goodbye(name: str):
print(f"### 系統:再會了,{name}!")
"""
prefix, suffix = full_code.split("<游標在這>", 1)
full_prompt = f"<fim_prefix>{prefix}<fim_suffix>{suffix}<fim_middle>"
outputs = generate(full_prompt)
得到以下輸出:
<fim_prefix>
def hello(name: str):
print(<fim_suffix>)
def goodbye(name: str):
print(f"### 系統:再會了,{name}!")
<fim_middle>f"### 系統:你好,{name}!"<|endoftext|>
最後透過一些後處理,將填充程式碼的生成結果重建回原本的程式碼:
tokens = tk.encode(full_prompt)
prompt = tk.decode(tokens, skip_special_tokens=True)
middle = tk.decode(outputs[0], skip_special_tokens=True)
middle = middle.replace(prompt, "")
print(full_code.replace("<游標在這>", middle))
得到最後完整的程式碼如下:
def hello(name: str):
print(f"### 系統:你好,{name}!")
def goodbye(name: str):
print(f"### 系統:再會了,{name}!")
這種先放 Prefix 再放 Suffix 最後接 Middle 的格式,也被稱為 PSM (Prefix Suffix Middle) 模式。另外也有先 Suffix 在 Prefix 最後 Middle 的版本,被稱為 SPM (Suffix Prefix Middle) 模式。
以上程式碼可以在 Colab Demo 裡面使用。
GitHub | HF Models | HF Datasets | HF Docs | StarChat Demo | Paper
CodeGen 是 Salesforce 訓練的一系列模型,包含 CodeGen, CodeGen2 與現在的 CodeGen2.5 這幾代。前幾代參數量都有出到 16B 的規格,但 CodeGen 2.5 只出了 7B 這個規格,這是為什麼呢?
根據 Salesforce 論文所述,原本 OpenAI 提出的縮放定律 (Scaling Law) 認為模型參數量與訓練資料量有個一定的比率,當資料量超出模型參數量的某個比例之後,模型的效果提升會出現邊際效應。另外,在訓練語言模型時,為了避免過度擬合 (Over Fitting) 的情況,通常一份文本只會訓練一次。
但 Salesforce 對這點抱持懷疑的態度,於是結合了 MLM 與 CLM 兩種建模方式,藉此對語言模型進行多個 Epochs 的訓練,最後得到效果相當好的 CodeGen 2.5 模型:
從論文的評估數據可見,這個 CodeGen2.5 雖然只有 7B 的參數量,但是很大幅度的打敗了 CodeGen2 16B 的模型,因此 Salesforce 相當有自信的推出僅 7B 大小的 CodeGen2.5 模型,並將部落格的介紹文章標題命名為「小而強大 (Small, but mighty)」。
筆者相當佩服這個實驗精神,研究團隊他們去質疑、去挑戰,雖然縮放定律有一定的參考性,但不該變成我們研究的侷限性,而 Salesforce 找到了一個方法去嘗試,並且因此而有所突破,我想這是科學研究裡面相當難能可貴的一件事情。
CodeGen2.5 分成三種版本:
除了一般的程式碼接龍以外,CodeGen2.5 同樣具有 FIM 的能力,其格式如下:
prefix + "<mask_1>" + suffix + "<|endoftext|>" + "<sep>" + "<mask_1>"
使用此格式進行推論時,最後會生成一個 <eom>
(End-Of-Mask) Token,可以根據此 Token 出現與否來進行截斷。
GitHub | Blog Post | HF Models | Paper
原本筆者很開心的抱著 CodeGen 在用,因為這個模型是我手邊一些實驗裡面性價比最高的,所以很長一段時間都很仰賴 CodeGen 的幫忙,直到我被 CodeLlama NTR 為止 ((x
CodeLlama 是從已經完成訓練的 Llama 2 繼續訓練而來的,使用 500B Tokens 資料量的程式碼,並加上約 8% 的自然語言資料來維持原本自然語言的能力。並透過 Long Context Fine-Tuning (LCFT) 的技巧,將 Llama 2 原本 4K 的模型長度擴展到了 16K,這個方法類似於 Position Interpolation (PI),透過改造 Rotary Position Embedding 來提高模型長度。
但是 Meta 的做法與原本的 PI 有些不同,未來有機會介紹一下這些拓展模型長度的方法。總而言之,最後 CodeLlama 實際使用時,可以處理輸入長度達 100K 的程式碼。
若想要存取 CodeLlama 需要去填寫官方表單申請,但也可以考慮直接使用 TheBloke 大神上傳的版本。
CodeLlama 同樣有 Infilling 的能力,根據官方 GitHub 提供的格式範例與 Tokenizer 程式碼,使用 CodeLlama 做 FIM 的格式應該如下:
prompt = f"▁<PRE> {prefix} ▁<SUF> {suffix} ▁<MID>"
生成的結果最後會附上一個 ▁<EOT>
Token 用來當作截斷輸出的依據。
雖然 Code LLMs 不如 General LLMs 那麼多,但筆者介紹的這些模型依然也只是冰山一角,其他還有 StableCode, WizzardCoder 和 ReplitCode 等等,族繁不及備載。這些開源的 Code LLMs 提供給開發者更多本地且輕量部署的選擇。模型的用途也從原本的程式碼接龍、程式碼填空一路走向對話形式,未來甚至還有機會朝著多模態 (Multimodal) 邁進,成為伴隨我們開發者更有力的助手。
接下來將會介紹 LLM 量化 (Quantization) 的概念,解釋這兩天經常看到的 load_in_8bit
與 load_in_4bit
到底是什麼意思,那我們明天見啦!